#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
//#include <libavcodec/videotoolbox.h>
#include <libavutil/avutil.h>
#include <stdio.h>

#define BUFSIZE 0x10000

int goReadPacket(void* p, void *buf, int size);
void goPushFrame();
AVFrame* goGetDecodingFrame();
void goNotifyStopped();

static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
    return goReadPacket(opaque, (void *) buf, buf_size);
}

int run_decoder(void *goDecoder) {
    AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "H.264 decoder not found\n");
        goto run_end;
    }

    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        fprintf(stderr, "Could not allocate decoder context\n");
        goto run_end;
    }

    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Could not open H.264 codec\n");
        goto run_finally_free_codec_ctx;
    }

    AVFormatContext *format_ctx = avformat_alloc_context();
    if (!format_ctx) {
        fprintf(stderr, "Could not allocate format context\n");
        goto run_finally_close_codec;
    }

    unsigned char *buffer = av_malloc(BUFSIZE);
    if (!buffer) {
        fprintf(stderr, "Could not allocate buffer\n");
        goto run_finally_free_format_ctx;
    }

    AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, goDecoder, read_packet, NULL, NULL);
    if (!avio_ctx) {
        fprintf(stderr, "Could not allocate avio context\n");
        // avformat_open_input takes ownership of 'buffer'
        // so only free the buffer before avformat_open_input()
        av_free(buffer);
        goto run_finally_free_format_ctx;
    }

    format_ctx->pb = avio_ctx;

    if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
        fprintf(stderr, "Could not open video stream\n");
        goto run_finally_free_avio_ctx;
    }

    AVPacket packet;
    av_init_packet(&packet);
    packet.data = NULL;
    packet.size = 0;

    while (!av_read_frame(format_ctx, &packet)) {
        int ret;
        if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
            fprintf(stderr, "Could not send video packet: %d\n", ret);
            goto run_quit;
        }
        AVFrame* frame = goGetDecodingFrame(goDecoder);
        ret = avcodec_receive_frame(codec_ctx, frame);
        if (!ret) {
            // a frame was received
            goPushFrame(goDecoder, frame);
        } else if (ret != AVERROR(EAGAIN)) {
            fprintf(stderr, "Could not receive video frame(2): %d\n", ret);
            av_packet_unref(&packet);
            goto run_quit;
        }

        av_packet_unref(&packet);

        if (avio_ctx->eof_reached) {
            break;
        }
    }

    fprintf(stderr, "End of frames\n");

run_quit:
    avformat_close_input(&format_ctx);
run_finally_free_avio_ctx:
    av_freep(&avio_ctx);
run_finally_free_format_ctx:
    avformat_free_context(format_ctx);
run_finally_close_codec:
    avcodec_close(codec_ctx);
run_finally_free_codec_ctx:
    avcodec_free_context(&codec_ctx);
    goNotifyStopped(goDecoder);
run_end:
    return 0;
}